/*
 * File:   main.c
 * Alunos: Daniel Santin Debastiani
 *         Dieison Soares Silveira
 *         Andrws Aires Vieira
 *
 *
 *
 * Trabalho de PC-1. EMULADOR
 * INSTRUÇÕES: No diretório do usuário deverá ser criado o arquivo texto com os
 *             opcodes das operações e endereços linha a linha.(TRABALHO QUE SERÁ REALIZADO PELO MONTADOR NO FUTURO)
 *             Deverá ser alterado os atributos fixos a fim de se adaptar a ambiente
 *             do usuário em questão.
 *  */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mensagens.h"                          //Importação do arquivo de mensagens

//Definição de atributos fixos
//#define caminhoArquivo "//home//daniel//"      //Diretório do usuário LINUX
//#define nomeArquivoBin "code.bin"               //Nome do arquivo binário que será abreto/criado

char nomeArquivo[255], caminhoArquivo[255];

//Definição de atributos operativos
#define FALHA 0                                 //Flag de falha
#define SUCESSO 1                               //Flag de Sucesso
#define TAMAHO_MEMORIA 1024                     // Tamanho da memória do emulador (1024 inteiros) = 2Kb
#define FALSE -1
#define TRUE -2

short int MEM[TAMAHO_MEMORIA];
short int REALOC[TAMAHO_MEMORIA];

//Protótipos das funções utilizadas
void emulador(int v);
int criaArquivoBin(char * caminhoAbsolutoBin, FILE * arquivoBin);
int trataDadosEntrada(char *palavra);
FILE * openFile();
void trataCaminhoArquivo(char * caminhoArq);

int main() {

    FILE * arquivoFonte;

    arquivoFonte = openFile();

    fseek(arquivoFonte, 0, SEEK_SET);

    FILE * arquivoRealoc;
    char caminhoAbsolutoRealoc[255] = {'\0'};

    strcat(caminhoAbsolutoRealoc, caminhoArquivo);
    strcat(caminhoAbsolutoRealoc, nomeArquivo);
    strcat(caminhoAbsolutoRealoc, ".realoc");

    //Criação do arquivo binário
    arquivoRealoc = fopen(caminhoAbsolutoRealoc, "rb");

    //Verificação do sucesso da criação do arquivo binário
    if (arquivoRealoc == NULL) {
        //erro
    }
    short int buffer, i;
    for (i = 0; !feof(arquivoRealoc); i++) {
        if (fread(&buffer, sizeof (short int), 1, arquivoRealoc)) {
            if (buffer > TAMAHO_MEMORIA) {
                //erro
            } else {
                REALOC[i] = buffer;
            }
        }
    }
    fclose(arquivoRealoc);

    /*
     * Declarando atributos do emulador
     * MEM: Memória - 1024 posições com 4Kb de tamanho
     * ERRO: Erro - flag de erro {0-false,1-true}
     */
    int ERRO = 0, pos, enderecoCarga;
    short int leitura = 11;

    printf("Endereço de carga: ");
    scanf("%d", &enderecoCarga);
    getchar();

    //Carregando arquivo binário para memória do emulador.
    for (i = 0, pos = enderecoCarga; !feof(arquivoFonte); i++, pos++) {

        //Verifica se não haverá estouro da memória
        if (pos > TAMAHO_MEMORIA) {
            printf(msgErroEstouroMemoria);
            ERRO = 1;
            break;
        }

        //Se a leitura do arquivo foi bem sicedia prossegue
        if (fread(&leitura, sizeof (short int), 1, arquivoFonte)) {
            if (REALOC[i] == TRUE) {
                MEM[pos] = leitura + enderecoCarga;
            } else {
                MEM[pos] = leitura;
            }
        }
    }

    //Fechando arquivo binário
    fclose(arquivoFonte);

    for (i = 0; i < TAMAHO_MEMORIA; i++) {
        printf("%d\n", MEM[i]);
    }

    //Se não houve erro (permanece em 0) chama o emulador
    if (ERRO == 0) {
        emulador(enderecoCarga);
        printf(infoFimPrograma);
    } else {
        printf(infoFimProgramaFalha);
    }

    //LOGGER: Mensagem de fim de execusão do pragrama bem sucedida
    return (EXIT_SUCCESS);


}

/**
 * Função responsável por emular o arquivo binário criado.
 **/
void emulador(int inicio) {

    //Definindo Opcodes simbólicos
#define ADD 2           //Operação de adição                : ACC ← ACC + OPD1
#define BR 0            //Operação de salto                 : Salta para OPD1
#define BRNEG 5         //Operação de salto condicional     : Salta para OPD1 se ACC < 0
#define BRPOS 1         //Operação de salto condicional     : Salta para OPD1 se ACC > 0
#define BRZERO 4        //Operação de salto condicional     : Salta para OPD1 se ACC = 0
#define COPY 13         //Operação de cópia                 : OPD2 ← OPD1
#define DIVIDE 10       //Operação de divisão               : ACC ← ACC / OPD1
#define LOAD 3          //Operação de carga                 : ACC ← OPD1
#define MULT 14         //Operação de multiplicação         : ACC ← ACC * OPD1
#define READ 12         //Operação de leitura de operando   : OPD1 ← entrada de dados
#define STOP 11         //Operação de parada                : Parar a execução
#define STORE 7         //Operação de gravação              : OPD1 ← ACC
#define SUB 6           //Operaçao de subtração             : ACC ← ACC - OPD1
#define WRITE 8         //Operação de escrita na tela       : Saída de dados ← OPD1

    /*
     * Declarando atributos do emulador
     * PC: Program Counter - iniciando em 0
     * AC: Acumulador - iniciando em 0
     * OP: Operacao - iniciando em 0
     */
    short int PC = inicio, AC = 0, OP = 0;
    char leitura[11] = {'\0'};

    while (OP != STOP) {

        //Verifica se a aplicação não tentará acessar uma área maior que a memória
        if (PC > TAMAHO_MEMORIA) {
            printf(msgErroAcessoMemoria);
            OP = STOP;
        } else {
            //Obtem opcode
            OP = MEM[PC];
        }

        //Seleciona operação. Se a operação não existir a aplicação emitirá um erro e terminará;
        switch (OP) {

            case ADD:
                AC += MEM[MEM[PC + 1]];
                PC += 2;
                break;

            case BR:
                PC = MEM[PC + 1];
                break;

            case BRNEG:
                if (AC < 0) {
                    PC = MEM[PC + 1];
                } else {
                    PC += 2;
                }
                break;

            case BRPOS:
                if (AC > 0) {
                    PC = MEM[PC + 1];
                } else {
                    PC += 2;
                }
                break;

            case BRZERO:
                if (AC == 0) {
                    PC = MEM[PC + 1];
                } else {
                    PC += 2;
                }
                break;

            case COPY:
                MEM[MEM[PC + 2]] = MEM[MEM[PC + 1]];
                PC += 3;
                break;

            case DIVIDE:
                if (MEM[MEM[PC + 1]] == 0) {
                    printf(msgErroDivisaoPorZero);
                    OP = STOP;
                    exit(EXIT_FAILURE);
                    break;
                } else {
                    AC /= MEM[MEM[PC + 1]];
                    PC += 2;
                    break;
                }

            case LOAD:
                AC = MEM[MEM[PC + 1]];
                PC += 2;
                break;

            case MULT:
                AC *= MEM[MEM[PC + 1]];
                PC += 2;
                break;

            case READ:
                do {
                    printf(requestOperando);
                    fgets(leitura, 11, stdin);
                    fflush(stdin); //Limpa buffer de teclado
                } while (trataDadosEntrada(leitura));
                MEM[MEM[PC + 1]] = atoi(leitura);
                PC += 2;
                break;

            case STOP:
                break;

            case STORE:
                MEM[MEM[PC + 1]] = AC;
                PC += 2;
                break;

            case SUB:
                AC -= MEM[MEM[PC + 1]];
                PC += 2;
                break;

            case WRITE:
                printf("%d\n", MEM[MEM[PC + 1]]);
                PC += 2;
                break;

            default:
                printf(msgErroOperacaoInvalida);
                OP = STOP;
                break;

        }
    }
}

/**
 * Funçao responsável por criar o arquivo binário a partir do conjunto de dados.
 * O conjunto é traduzido para o arquivo binário e a função retorna sucesso.
 **/
int criaArquivoBin(char * caminhoAbsolutoBin, FILE * arquivoBin) {

    //LOGGER
    // printf(infoCriandoArquivoBin);

    //Criação do arquivo binário
    arquivoBin = fopen(caminhoAbsolutoBin, "wb");

    //Verificação do sucesso da criação do arquivo binário
    if (arquivoBin == NULL) {
        return EXIT_FAILURE;
    }

    //Buffer de leitura
    int i;
    //Conjunto de dados (simula o arquivo com os opcodes)
    short int code[] = {12, 139, 12, 149, 3, 139, 10, 149, 7, 159, 8, 159, 11};

    //Posicionando os ponteiros de arquivo no ínicio do arquivo
    fseek(arquivoBin, 0, SEEK_SET);

    for (i = 0; i < 13; i++) {
        fwrite(&code[i], sizeof (short int), 1, arquivoBin);
    }

    //Fechamento dos arquivos
    fclose(arquivoBin);

    return EXIT_SUCCESS;
}

/**
 * Funcao para fazer o tratamento da entrada de dados no READ
 * invalida entrada com caracteres nao numericos e espacos em branco.
 **/
int trataDadosEntrada(char *palavra) {
    int i;
    char letra;

    for (i = 0; palavra[i] != '\n'; i++) {
        letra = palavra[i];
        if (!(letra >= '0' && letra <= '9')) {
            printf(msgErroEntradaDados);
            return EXIT_FAILURE;
        }
    }
    return EXIT_SUCCESS;
}

/*
 * Responsável pela abertura do arquivo.
 * Caso o arquivo exista retorna um ponteiro para o mesmo.
 */
FILE * openFile() {
    char strArq[255];
    FILE * arquivo;
    printf("Informe o arquivo a ser lido: ");
    scanf("%s", strArq);
    getchar();

    trataCaminhoArquivo(strArq);

    if ((arquivo = fopen(strArq, "r")) == NULL) {
        printf("O arquivo especificado não existe!\n");
        exit(EXIT_FAILURE);
    }

    return arquivo;
}

void trataCaminhoArquivo(char * caminhoArq) {

    int fim = strlen(caminhoArq);
    int iExtencao, iBarra, i = 0;
    for (iExtencao = fim; caminhoArq[iExtencao] != '.'; --iExtencao);
    for (iBarra = fim; caminhoArq[iBarra] != '/'; --iBarra);
    for (fim = iBarra + 1; fim < iExtencao; fim++, i++) {
        nomeArquivo[i] = caminhoArq[fim];
    }
    for (i = 0; i <= iBarra; i++) {
        caminhoArquivo[i] = caminhoArq[i];
    }
}